bugfix(object): Avoid crash with dangling contain module in Object::onDestroy() when Reinforcement Pad is destroyed before Troop Crawler drop#2747
Conversation
|
| Filename | Overview |
|---|---|
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp | Core implementation of the workaround; onContainedBy, onRemovedFrom, onDestroy, and xfer all updated correctly; previously flagged xfer-save re-derivation issue is now guarded. |
| Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp | Mirrors the GeneralsMD changes; all four modified functions are consistent with the Zero Hour copy. |
| GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h | Member renamed from m_xferContainedByID to m_containedByID with updated documentation comment reflecting its broadened purpose. |
| Generals/Code/GameEngine/Include/GameLogic/Object.h | Same header rename as GeneralsMD; no logic changes. |
Sequence Diagram
sequenceDiagram
participant A as Object A (Container)
participant B as Object B (Contained)
participant GL as GameLogic
participant OC as OpenContain (A's module)
Note over A,B: Normal containment setup
A->>B: onContainedBy(A)
B->>B: "m_containedBy = A, m_containedByID = A.getID()"
Note over A,OC: Container destruction begins (bug scenario)
GL->>A: destroyObject(A)
A->>OC: onDelete() — m_containList already freed!
OC->>B: "onContainedBy(A) where A.isDestroyed()==true"
B->>B: "m_containedBy = A (dangling!), m_containedByID = INVALID_ID"
Note over B,GL: Contained object is also destroyed
GL->>B: destroyObject(B)
B->>B: onDestroy()
B->>B: "m_containedBy != null — enter block"
alt "m_containedByID == INVALID_ID (container already destroyed)"
B->>B: DEBUG_CRASH / skip removeFromContain (safe)
else ID valid (normal path)
B->>GL: findObjectByID(m_containedByID)
GL-->>B: returns m_containedBy (confirmed alive)
B->>OC: removeFromContain(B) — safe
end
Reviews (7): Last reviewed commit: "Replicated in Generals." | Re-trigger Greptile
3c40642 to
69cfc9b
Compare
da36906 to
1af8f31
Compare
|
I just did a test in multiplayer with local instances and noticed 6 places where the game would access the container object after destruction. I noticed because the game would crash if the memory is overwritten on deallocation, as is the case with The following 5 could be rewritten like so: An early return if |
Is this for a follow up or what do we do with this? |
Nothing as far as I'm concerned. It's just an explicit acknowledgement that the 'damage' for this bug is so extensive that I don't see how it can be fixed with retail compatibility, but at least it shouldn't crash. |
7635f14 to
756de02
Compare
Resolved merge conflicts in 'Object.cpp'.
|
Replicated in Generals. |
Upstream: TheSuperHackers/main @ 7c78a5e Ancestor: 20f4254 (previous sync) Divergence: 5 commits upstream / 1813 ours (small sync) PRs brought in: - TheSuperHackers#2710 fix(memory): Fix various memory leaks (2) - TheSuperHackers#2747 bugfix(object): Avoid crash with dangling contain module (Object::onDestroy) - TheSuperHackers#2718 fix/refactor(milesaudiomanager): Prevent multithread crashing + simplify - TheSuperHackers#2577 fix(metaevent): Ignore order in which modifier keys are released - TheSuperHackers#2758 refactor(metaevent): Split MetaEventTranslator::translateGameMessage() Conflict resolution (this commit): - Generals/GeneralsMD/.../MetaEvent.h: replaced m_lastKeyDown/m_lastModState members with upstream's KeyDownInfo m_keyDownInfos[KEY_COUNT] struct to match the refactored translator (auto-merge had unioned both). - Generals/GeneralsMD/.../MetaEvent.cpp: resolved UU by taking upstream's onKeyEvent() dispatch + dropped now-dead m_lastKeyDown/m_lastModState member initializers in the constructor. - Core/GameEngine/Include/Common/GameAudio.h: kept upstream's signature change (nextMusicTrack/prevMusicTrack now return AsciiString; getMusicTrackName removed from base). Updated our OpenAL backend (Core/GameEngineDevice/{Include,Source}/OpenALAudioDevice/) to match. - Core/Libraries/Include/Lib/BaseType.h: added missing BitsAreSet macro that upstream's MetaEvent code depends on. - AGENTS.md: cleared pre-existing merge markers (not from this sync; left from commit 996eedd). - GeneralsMD/.../Source/OpenALAudioManager.cpp stub: signature parity. Preserved (no merge change): - GeneralsX cross-platform stack: SDL3, DXVK, OpenAL, FFmpeg - All .github/ CI/CD infrastructure intact - GeneralsX-specific patches and GeneralsX @BugFix annotations Verified: - macos-vulkan configure + build (GeneralsX + GeneralsXZH) - Runtime smoke for both products (clean init, no crash) - No merge markers in tree (excluding build/ artifacts) - Cross-platform audit: no platform-specific code leaked into shared headers; SDL3/DXVK/OpenAL/FFmpeg layers untouched by upstream Deferred to CI: - linux64-deploy configure + build (validated locally but heavy docker image build; CI covers) See docs/WORKDIR/planning/PLAN-2026-06-04_THESUPERHACKERS_SYNC.md for the full conflict resolution report and risk analysis. Refs: TheSuperHackers#2710, TheSuperHackers#2747, TheSuperHackers#2718, TheSuperHackers#2577, TheSuperHackers#2758
Upstream: TheSuperHackers/main @ 7c78a5e Ancestor: 20f4254 (previous sync) Divergence: 5 commits upstream / 1813 ours (small sync) PRs brought in: - TheSuperHackers#2710 fix(memory): Fix various memory leaks (2) - TheSuperHackers#2747 bugfix(object): Avoid crash with dangling contain module (Object::onDestroy) - TheSuperHackers#2718 fix/refactor(milesaudiomanager): Prevent multithread crashing + simplify - TheSuperHackers#2577 fix(metaevent): Ignore order in which modifier keys are released - TheSuperHackers#2758 refactor(metaevent): Split MetaEventTranslator::translateGameMessage() Conflict resolution (this commit): - Generals/GeneralsMD/.../MetaEvent.h: replaced m_lastKeyDown/m_lastModState members with upstream's KeyDownInfo m_keyDownInfos[KEY_COUNT] struct to match the refactored translator (auto-merge had unioned both). - Generals/GeneralsMD/.../MetaEvent.cpp: resolved UU by taking upstream's onKeyEvent() dispatch + dropped now-dead m_lastKeyDown/m_lastModState member initializers in the constructor. - Core/GameEngine/Include/Common/GameAudio.h: kept upstream's signature change (nextMusicTrack/prevMusicTrack now return AsciiString; getMusicTrackName removed from base). Updated our OpenAL backend (Core/GameEngineDevice/{Include,Source}/OpenALAudioDevice/) to match. - Core/Libraries/Include/Lib/BaseType.h: added missing BitsAreSet macro that upstream's MetaEvent code depends on. - AGENTS.md: cleared pre-existing merge markers (not from this sync; left from commit 996eedd). - GeneralsMD/.../Source/OpenALAudioManager.cpp stub: signature parity. Preserved (no merge change): - GeneralsX cross-platform stack: SDL3, DXVK, OpenAL, FFmpeg - All .github/ CI/CD infrastructure intact - GeneralsX-specific patches and GeneralsX @BugFix annotations Verified: - macos-vulkan configure + build (GeneralsX + GeneralsXZH) - Runtime smoke for both products (clean init, no crash) - No merge markers in tree (excluding build/ artifacts) - Cross-platform audit: no platform-specific code leaked into shared headers; SDL3/DXVK/OpenAL/FFmpeg layers untouched by upstream Deferred to CI: - linux64-deploy configure + build (validated locally but heavy docker image build; CI covers) See docs/WORKDIR/planning/PLAN-2026-06-04_THESUPERHACKERS_SYNC.md for the full conflict resolution report and risk analysis. Refs: TheSuperHackers#2710, TheSuperHackers#2747, TheSuperHackers#2718, TheSuperHackers#2577, TheSuperHackers#2758
This PR provides a retail specific workaround for use-after-free bug that could crash the game under very specific conditions (see issue and related PR).
The actual crash would happen when e.g.
OpenContain::m_containListis accessed after it's been destructed. STLPort and MSVC implementations forstd::listrely on dynamically allocated memory, so accessing the begin / end iterator after the destruction of the list is not just undefined behavior, it crashes the game. See minimal crash reproduction: https://godbolt.org/z/T9sEb4MsKTODO: